local ffi = require "ffi"
local math_ext = require "math_extended"

ffi.cdef [[
    typedef struct { float x, y; } vec2_t;
]]

local vec2_t = ffi.typeof("vec2_t")


local vec2

---@class vec2_mt
local mt = {
    ---@param _ vec2
    ---@param x number
    ---@param y number
    ---@return vec2
    __new = function(_, x, y)
        if x and y then
            return ffi.new(vec2_t, { x, y })
        elseif x and not y then
            return ffi.new(vec2_t, { x, x })
        elseif not (x or y) then
            return ffi.new(vec2_t, { 0, 0 })
        end

        error("x, y must be not a nil")
    end,

    ---@param self vec2
    ---@param other vec2
    ---@return vec2
    __add = function(self, other)
        return vec2(self.x + other.x, self.y + other.y)
    end,

    ---@param self vec2
    ---@param other vec2|number
    ---@return vec2
    __mul = function(self, other)
        if type(other) == "number" then
            return vec2(self.x * other, self.y * other)
        end

        return vec2(self.x * other.x, self.y * other.y)
    end,

    ---@param self vec2
    ---@param other vec2
    ---@return vec2
    __sub = function(self, other)
        return vec2(self.x - other.x, self.y - other.y)
    end,

    __div = function(self, other)
        if type(other) == "number" then
            return vec2(self.x / other, self.y / other)
        elseif type(other) == "cdata" then
            return vec2(self.x / other.x, self.y / other.y)
        end
    end,

    ---@param self vec2
    ---@param other vec2
    __eq = function(self, other)
        return self.x == other.x and self.y == other.y
    end,

    __len = function(_)
        return 2
    end,

    __tostring = function(self)
        return "vec2(" .. self.x .. ", " .. self.y .. ")"
    end,

    ---@param _ vec2
    ---@param x vec2
    ---@param y vec2
    ---@return vec2
    __call = function(_, x, y)
        return { x, y }
    end,

    ---@param min vec2
    ---@param max vec2
    ---@return vec2
    clamp = function(self, min, max)
        return vec2(
            math_ext.clamp(self.x, min.x, max.x), 
            math_ext.clamp(self.y, min.y, max.y)
        )
    end,

    ---@param other vec2
    ---@return vec2
    min = function (self, other)
        return vec2(math.min(self.x, other.x), math.min(self.y, other.y))
    end,

    ---@param other vec2
    ---@return vec2
    max = function (self, other)
        return vec2(math.max(self.x, other.x), math.max(self.y, other.y))
    end,


    ---@param self vec2
    ---@return vec2
    copy = function(self) return vec2(self.x, self.y) end,
}

mt.__index = mt

---@class vec2 : vec2_mt
---@field x number
---@field y number
---@operator call:vec2
---@operator add:vec2
---@operator sub:vec2
---@operator mul:vec2
---@operator div:vec2
vec2 = ffi.metatype("vec2_t", mt)

return vec2
